// scaup - Scala up!
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: Parser.scala 24 2009-11-08 16:16:27Z normen.mueller $
package locutor.parsing.model.dg

import java.io.File
import java.net.URL
import java.text.MessageFormat.{format}

import collection.mutable.HashSet

import scalaxx.path._

import scaup.Views._

import locutor.model.{DependencyGraph, GraphModel}
import locutor.parsing.model.gm.GraphModelParser.{parse}

/** A dependency graph parser.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [dg] class Parser extends BaseParser {
  /* Fill in abstract defs*/
  type Tokens = Lexer
  val lexical: Tokens = new Lexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List("graph", "for", "at", "using")
  lexical.delimiters ++= List("{", "}", "(", ")", "-:", ":", "=", ";", ",", "->")

  /* Define the grammar */  
  def dg(description: File) = graph(description) >> constituents
  
  def graph(description: File) = (("graph" ~> text) ~ ("for" ~>  string) ~ ("at" ~> numeric))  ~ ("using" ~> model) ^^ {
    case name ~ ref ~ rev ~ model =>
      new DependencyGraph(name, ref.toFile.resolveWrt(description).toFile, rev.toInt, model)      
  }
  
  lazy val model = string ^^ {
    case model => ((m: String) =>
      parse((m.toLowerCase + ".gm").toFile)      
    )(model replaceFirst("\\$LOCUTOR_MODELS", System.getProperty("locutor.models")))
  }
  
  lazy val constituents = (g: DependencyGraph) => ("{" ~> (nodes(g) >> edges) <~ "}")
  
  lazy val nodes = (g: DependencyGraph) => (((text ~ (":" ~> ident) ~ attributes))*) ^^ {
    case nodes =>
      for(node <- nodes) node match {
        case id ~ ty ~ as =>
          val url = (as get "url") match {
            case Some(u) => u.toURL
            case None    => g.reference.path.toFile.toURL
          }
          val xpath = (as get "xpath") match {
            //@todo eliminate asInstanceOf
            case Some(x) => XPath.parse(x).asInstanceOf[LocationPath]
            case None    => error("Fatal DG parsing error: '" + id + "' is missing XPath attribute")
          }
          g + (id, url, xpath, (Map("type" -> ty) ++ (as - "url" - "xpath")))
      }
      g
  }
  
  lazy val edges = (g: DependencyGraph) => ((text ~ ("-:" ~> ident) ~ opt(attributes) ~ ("->" ~> text))*) ^^ {
    case edges => 
      for(edge <- edges) edge match {
        case src ~ ty ~ as ~ tgt => (g \ src, g \ tgt) match {
          case (Some(s), Some(t)) => (s connectTo t)(Map("type" -> ty) ++ as.getOrElse(Nil))
          case _                  => error("Fatal DG parsing error: Some edge declaration is not syntax conform.")
        }
      }
      g
  }
  
  lazy val attributes = "(" ~> ((attribute <~ opt(","))*) <~ ")" ^^ { case as => Map() ++ as }
                                                                
  lazy val attribute = (ident <~ "=") ~ (text | numeric) ^^ { case k ~ v => (k, v) }

}
